#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Collections;
using System.Globalization;

using log4net.Core;
using log4net.Layout;

namespace log4net.Util
{
  /// <summary>
  /// Most of the work of the <see cref="PatternLayout"/> class
  /// is delegated to the PatternParser class.
  /// </summary>
  /// <remarks>
  /// <para>
  /// The <c>PatternParser</c> processes a pattern string and
  /// returns a chain of <see cref="PatternConverter"/> objects.
  /// </para>
  /// </remarks>
  /// <author>Nicko Cadell</author>
  /// <author>Gert Driesen</author>
  public sealed class PatternParser
  {
    #region Public Instance Constructors

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="pattern">The pattern to parse.</param>
    /// <remarks>
    /// <para>
    /// Initializes a new instance of the <see cref="PatternParser" /> class 
    /// with the specified pattern string.
    /// </para>
    /// </remarks>
    public PatternParser(string pattern)
    {
      m_pattern = pattern;
    }

    #endregion Public Instance Constructors

    #region Public Instance Methods

    /// <summary>
    /// Parses the pattern into a chain of pattern converters.
    /// </summary>
    /// <returns>The head of a chain of pattern converters.</returns>
    /// <remarks>
    /// <para>
    /// Parses the pattern into a chain of pattern converters.
    /// </para>
    /// </remarks>
    public PatternConverter Parse()
    {
      string[] converterNamesCache = BuildCache();

      ParseInternal(m_pattern, converterNamesCache);

      return m_head;
    }

    #endregion Public Instance Methods

    #region Public Instance Properties

    /// <summary>
    /// Get the converter registry used by this parser
    /// </summary>
    /// <value>
    /// The converter registry used by this parser
    /// </value>
    /// <remarks>
    /// <para>
    /// Get the converter registry used by this parser
    /// </para>
    /// </remarks>
    public Hashtable PatternConverters
    {
      get { return m_patternConverters; }
    }

    #endregion Public Instance Properties

    #region Private Instance Methods

    /// <summary>
    /// Build the unified cache of converters from the static and instance maps
    /// </summary>
    /// <returns>the list of all the converter names</returns>
    /// <remarks>
    /// <para>
    /// Build the unified cache of converters from the static and instance maps
    /// </para>
    /// </remarks>
    private string[] BuildCache()
    {
      string[] converterNamesCache = new string[m_patternConverters.Keys.Count];
      m_patternConverters.Keys.CopyTo(converterNamesCache, 0);

      // sort array so that longer strings come first
      Array.Sort(converterNamesCache, 0, converterNamesCache.Length, StringLengthComparer.Instance);

      return converterNamesCache;
    }

    #region StringLengthComparer

    /// <summary>
    /// Sort strings by length
    /// </summary>
    /// <remarks>
    /// <para>
    /// <see cref="IComparer" /> that orders strings by string length.
    /// The longest strings are placed first
    /// </para>
    /// </remarks>
    private sealed class StringLengthComparer : IComparer
    {
      public static readonly StringLengthComparer Instance = new StringLengthComparer();

      private StringLengthComparer()
      {
      }

      #region Implementation of IComparer

      public int Compare(object x, object y)
      {
        string s1 = x as string;
        string s2 = y as string;

        if (s1 == null && s2 == null)
        {
          return 0;
        }
        if (s1 == null)
        {
          return 1;
        }
        if (s2 == null)
        {
          return -1;
        }

        return s2.Length.CompareTo(s1.Length);
      }

      #endregion
    }

    #endregion // StringLengthComparer

    /// <summary>
    /// Internal method to parse the specified pattern to find specified matches
    /// </summary>
    /// <param name="pattern">the pattern to parse</param>
    /// <param name="matches">the converter names to match in the pattern</param>
    /// <remarks>
    /// <para>
    /// The matches param must be sorted such that longer strings come before shorter ones.
    /// </para>
    /// </remarks>
    private void ParseInternal(string pattern, string[] matches)
    {
      int offset = 0;
      while (offset < pattern.Length)
      {
        int i = pattern.IndexOf('%', offset);
        if (i < 0 || i == pattern.Length - 1)
        {
          ProcessLiteral(pattern.Substring(offset));
          offset = pattern.Length;
        }
        else
        {
          if (pattern[i + 1] == '%')
          {
            // Escaped
            ProcessLiteral(pattern.Substring(offset, i - offset + 1));
            offset = i + 2;
          }
          else
          {
            ProcessLiteral(pattern.Substring(offset, i - offset));
            offset = i + 1;

            FormattingInfo formattingInfo = new FormattingInfo();

            // Process formatting options

            // Look for the align flag
            if (offset < pattern.Length)
            {
              if (pattern[offset] == '-')
              {
                // Seen align flag
                formattingInfo.LeftAlign = true;
                offset++;
              }
            }
            // Look for the minimum length
            while (offset < pattern.Length && char.IsDigit(pattern[offset]))
            {
              // Seen digit
              if (formattingInfo.Min < 0)
              {
                formattingInfo.Min = 0;
              }

              formattingInfo.Min = (formattingInfo.Min * 10) + int.Parse(pattern[offset].ToString(), NumberFormatInfo.InvariantInfo);

              offset++;
            }
            // Look for the separator between min and max
            if (offset < pattern.Length)
            {
              if (pattern[offset] == '.')
              {
                // Seen separator
                offset++;
              }
            }
            // Look for the maximum length
            while (offset < pattern.Length && char.IsDigit(pattern[offset]))
            {
              // Seen digit
              if (formattingInfo.Max == int.MaxValue)
              {
                formattingInfo.Max = 0;
              }

              formattingInfo.Max = (formattingInfo.Max * 10) + int.Parse(pattern[offset].ToString(), NumberFormatInfo.InvariantInfo);

              offset++;
            }

            int remainingStringLength = pattern.Length - offset;

            // Look for pattern
            for (int m = 0; m < matches.Length; m++)
            {
              string key = matches[m];

              if (key.Length <= remainingStringLength)
              {
                if (string.Compare(pattern, offset, key, 0, key.Length) == 0)
                {
                  // Found match
                  offset = offset + matches[m].Length;

                  string option = null;

                  // Look for option
                  if (offset < pattern.Length)
                  {
                    if (pattern[offset] == '{')
                    {
                      // Seen option start
                      offset++;

                      int optEnd = pattern.IndexOf('}', offset);
                      if (optEnd < 0)
                      {
                        // error
                      }
                      else
                      {
                        option = pattern.Substring(offset, optEnd - offset);
                        offset = optEnd + 1;
                      }
                    }
                  }

                  ProcessConverter(matches[m], option, formattingInfo);
                  break;
                }
              }
            }
          }
        }
      }
    }

    /// <summary>
    /// Process a parsed literal
    /// </summary>
    /// <param name="text">the literal text</param>
    private void ProcessLiteral(string text)
    {
      if (text.Length > 0)
      {
        // Convert into a pattern
        ProcessConverter("literal", text, new FormattingInfo());
      }
    }

    /// <summary>
    /// Process a parsed converter pattern
    /// </summary>
    /// <param name="converterName">the name of the converter</param>
    /// <param name="option">the optional option for the converter</param>
    /// <param name="formattingInfo">the formatting info for the converter</param>
    private void ProcessConverter(string converterName, string option, FormattingInfo formattingInfo)
    {
      LogLog.Debug(declaringType, "Converter [" + converterName + "] Option [" + option + "] Format [min=" + formattingInfo.Min + ",max=" + formattingInfo.Max + ",leftAlign=" + formattingInfo.LeftAlign + "]");

      // Lookup the converter type
      ConverterInfo converterInfo = (ConverterInfo)m_patternConverters[converterName];
      if (converterInfo == null)
      {
        LogLog.Error(declaringType, "Unknown converter name [" + converterName + "] in conversion pattern.");
      }
      else
      {
        // Create the pattern converter
        PatternConverter pc = null;
        try
        {
          pc = (PatternConverter)Activator.CreateInstance(converterInfo.Type);
        }
        catch (Exception createInstanceEx)
        {
          LogLog.Error(declaringType, "Failed to create instance of Type [" + converterInfo.Type.FullName + "] using default constructor. Exception: " + createInstanceEx.ToString());
        }

        // formattingInfo variable is an instance variable, occasionally reset 
        // and used over and over again
        pc.FormattingInfo = formattingInfo;
        pc.Option = option;
        pc.Properties = converterInfo.Properties;

        IOptionHandler optionHandler = pc as IOptionHandler;
        if (optionHandler != null)
        {
          optionHandler.ActivateOptions();
        }

        AddConverter(pc);
      }
    }

    /// <summary>
    /// Resets the internal state of the parser and adds the specified pattern converter 
    /// to the chain.
    /// </summary>
    /// <param name="pc">The pattern converter to add.</param>
    private void AddConverter(PatternConverter pc)
    {
      // Add the pattern converter to the list.

      if (m_head == null)
      {
        m_head = m_tail = pc;
      }
      else
      {
        // Set the next converter on the tail
        // Update the tail reference
        // note that a converter may combine the 'next' into itself
        // and therefore the tail would not change!
        m_tail = m_tail.SetNext(pc);
      }
    }

    #endregion Protected Instance Methods

    #region Private Constants

    private const char ESCAPE_CHAR = '%';

    #endregion Private Constants

    #region Private Instance Fields

    /// <summary>
    /// The first pattern converter in the chain
    /// </summary>
    private PatternConverter m_head;

    /// <summary>
    ///  the last pattern converter in the chain
    /// </summary>
    private PatternConverter m_tail;

    /// <summary>
    /// The pattern
    /// </summary>
    private string m_pattern;

    /// <summary>
    /// Internal map of converter identifiers to converter types
    /// </summary>
    /// <remarks>
    /// <para>
    /// This map overrides the static s_globalRulesRegistry map.
    /// </para>
    /// </remarks>
    private Hashtable m_patternConverters = new Hashtable();

    #endregion Private Instance Fields

    #region Private Static Fields

    /// <summary>
    /// The fully qualified type of the PatternParser class.
    /// </summary>
    /// <remarks>
    /// Used by the internal logger to record the Type of the
    /// log message.
    /// </remarks>
    private static readonly Type declaringType = typeof(PatternParser);

    #endregion Private Static Fields
  }
}
